package remote;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * This class is a serialized class that wrapes all invocations 
 * @author sashas
 *
 */
public  class RemoteInvocation implements Externalizable{
	
	private static final long serialVersionUID = -7107561254813273937L;

	/** Keeps class name of the invocation*/
	private Class<?> theClass;
	
	
	/** Keeps name of the method to invoke*/
	private Method method;
	
	/** keeps parameters of the method call*/
	private Serializable[] methodParams;
	
	/** Keeps session id of the client session*/
	private long sessionID;
	
	/**
	 * Public ctor for reconstruction
	 */
	public RemoteInvocation(){
		
	}
	
	/**
	 * @param remoteClass - class for the invocation 
	 * @param method      - invocation method 
	 * @param sessionID   - id of the established session (-1 if operating as guest)
	 * @param arguments   - arguments of the class
	 */
	public RemoteInvocation(Class<?> remoteClass,Method method,long sessionID,Serializable... arguments){
		this.theClass = remoteClass;
		this.method = method;
		this.methodParams = arguments;		
		this.sessionID = sessionID;
	}
	
	
	@Override
	public void readExternal(ObjectInput in) throws IOException,
			ClassNotFoundException {
		
		
		theClass = Class.forName(in.readUTF());
		String methodName = in.readUTF();
		sessionID = in.readLong();
		
		short paramsNo = in.readShort();
		
		methodParams = new Serializable[paramsNo];
		Class<?>[] paramsClassType = new Class<?>[paramsNo];
		
		for (int paramsCtr = 0; paramsCtr < paramsNo; paramsCtr++){
			methodParams[paramsCtr] = (Serializable)in.readObject();
			paramsClassType[paramsCtr] = methodParams[paramsCtr].getClass();
		}
		
		try{
			
			boolean methodFound = false;
			// Here we have to find the correct method with correct number of arguments .. 
			// we wont support overloading with argument type 
			for (Method classMethod : theClass.getMethods()){
			
				// Check if the name matches 
				if  ( (classMethod.getName().equals(methodName)) &&
					  (classMethod.getParameterTypes().length == paramsNo)){
					
					// Now pass through all paramers and check if the passed parameters can be an instance of the 
					// parameters required by the method
					boolean parametersMath = true;
					for (int classParamsCtr = 0; ((parametersMath) && (classParamsCtr < paramsNo)); classParamsCtr++){
						
						Class methodParamType = classMethod.getParameterTypes()[classParamsCtr];
						Class passedParamType = paramsClassType[classParamsCtr];
						
						if (!methodParamType.isAssignableFrom(passedParamType)){
							parametersMath = false;
						}
						
					}
					
					if (parametersMath){
						this.method = classMethod;
						methodFound = true;
						break;
					}

				}
			}
			
			if (!methodFound){
				throw new NoSuchMethodException("Cannot find method '" + methodName + "' with parameters '" + 
						paramsClassType + "'");
			}
		}catch(NoSuchMethodException ex){
			throw new ClassNotFoundException("Method '" + methodName + "' is not found or does not support params '" 
						+ Arrays.toString(paramsClassType) + "'");
		}
		
	}
	@Override
	public void writeExternal(ObjectOutput out) throws IOException {
				
		out.writeUTF(theClass.getName());
		out.writeUTF(method.getName());
		out.writeLong(sessionID);
	    out.writeShort(methodParams.length);
		for (Serializable param: methodParams){
			out.writeObject(param);
		}		
	}
	
	
	public static long getSerialversionuid() {
		return serialVersionUID;
	}

	public Class<?> getInvocationClass() {
		return theClass;
	}

	public Method getMethod() {
		return method;
	}

	public Serializable[] getMethodParams() {
		return methodParams;
	}

	public long getSessionID() {
		return sessionID;
	}
	
	
	
}
